/* eslint-disable no-mixed-operators, no-control-regex */
/**
 *
 * 132所报告
 *
 * Created by yangyunze on 2024/06/04
 */

const fs = require('fs-extra');
const path = require('path');
const compressing = require('compressing');
const moment = require('moment');
const { Types } = require('mongoose');

const Docx = require('./../adapter/docx');
const DefaultReporter = require('./../default-report');
const ProjectVersionSpliter = require('../../project-version-spliter');
const fileElements = require('./../../../file-elements');
const utility = require('./../../../utility');
const { kind: fileKind } = require('./../../../files');
const File = require('./../../../mongo/file');

class Reporter extends DefaultReporter {
  constructor(versionId, locale, version = null, project = null, creator = null, outputDir = null, format, complete) {
    super(versionId, locale, version, project, creator, outputDir, format, complete);
    this.baseDir = __dirname;
    this.globalTestIdx = 0;
    this.errorTests = [];
    this.totalTests = 0;
    this.unknownTests = 0;
    this.passedTests = 0;
    this.totalLine = 0;
  }

  async generate(progressIndicator) {
    const fileName = '软件单元测试报告';
    const result = {
      cachePath: this.outputDir,
    };
    const { versionPath, projectDir } = await this.spliteByProjectVersion();
    this.versionDir = versionPath;
    this.projectDir = projectDir;
    let reportPath = `${path.join(this.versionDir, '软件CSCI合格性测试说明')}`;
    const files = await this.spliteByFile(progressIndicator);
    await this.generateFileReport(
      {
        projectName: this.project.projectName,
        versionName: this.version.versionName,
        language: this.version.language,
        functions: files.reduce((pre, file) => {
          if (file.functions.length === 0) {
            return pre;
          }
          return pre.concat(file.functions);
        }, []),
      },
      reportPath,
      'file-template'
    );
    reportPath = `${path.join(this.versionDir, '单元测试报告')}`;
    await this.generateFileReport(
      {
        projectName: this.project.projectName,
        versionName: this.version.versionName,
        language: this.version.language,
        errorTests: this.errorTests.map((err, idx) => {
          err.idx = idx + 1;
          err.index = (idx + 1).toString().padStart(3, '0');
          return err;
        }),
        totalLine: this.totalLine,
        kloc: Math.floor(this.errorTests.length / this.totalLine * 1000),
        totalTests: this.totalTests,
        unknownTests: this.unknownTests,
        passedTests: this.passedTests,
        passedRate: Math.round(this.passedTests / this.totalTests * 10000 / 100),
        projectCreateTime: moment(Types.ObjectId(this.project._id).getTimestamp()).format('YYYYMMDD'),
      },
      reportPath,
      'file-template-failed'
    );
    result.reportPath = path.join(this.outputDir, `${fileName}.zip`);
    const reportsDir = path.join(this.outputDir, this.project.projectName);
    await compressing.zip.compressDir(reportsDir, result.reportPath);
    return result;
  }

  async spliteByFile(progressIndicator) {
    await fs.ensureDir(this.versionDir);
    const sourceFileExts = [];
    const languages = { ...this.version.languages, headers: this.version.headers };
    Object.keys(languages).forEach((key) => {
      if (languages[key].suffixes) {
        sourceFileExts.push(...languages[key].suffixes);
      }
    });

    const resFiles = new Map();
    const filter = {
      versionId: this.versionId,
      $or: [
        { kind: fileKind.file },
        { kind: fileKind.fileOutsideProject },
      ],
      $and: [{ fileName: { $nin: ['.gitignore', '.smartrocket'] } }],
    };
    if (sourceFileExts && sourceFileExts.length > 0) {
      filter.$or.push({
        exname: { $in: sourceFileExts },
      });
    }
    const allFiles = await File.find(filter, ['_id', 'fileName', 'path', 'sourceLines']).lean();
    allFiles.forEach((file) => {
      resFiles.set(file._id.toString(), file);
      this.totalLine += file.sourceLines;
    });
    return this.handleFiles([...resFiles.values()], this.versionDir, progressIndicator);
  }

  async handleFiles(files, versionDir, progressIndicator) {
    return utility.arrayChunkOperation(files, 1, async (file) => {
      const result = await this.handleFile(file, versionDir);
      if (progressIndicator) {
        progressIndicator.advance(1 / files.length);
      }
      return result;
    });
  }

  async handleFile(file, versionDir) {
    const res = await ProjectVersionSpliter.prototype.handleFile.call(this, file, versionDir);
    const { typeSystem, functions } = res;
    const fileSummary = DefaultReporter.getSummaryOfFile(functions);
    const result = {
      fileName: file.fileName,
      filePath: file.path,
      summary: fileSummary,
      functions: functions.filter((func) => fileElements.isTestableElement(func.kind) && func.tests.length > 0).map((func) => {
        const testInfoMap = {};
        func.tests.forEach((test) => {
          testInfoMap[test._id.toString()] = {
            log: test.log,
          };
        });
        const result = {};
        result.summary = this.getSummaryOfFunction(func);
        result.tests = this.getTestDetailsOfFunction(typeSystem, func);
        result.functionName = func.functionName;
        result.tests.forEach((test, index) => {
          this.globalTestIdx += 1;
          test.globalIdx = this.globalTestIdx;
          this.totalTests += 1;
          test.index = (index + 1).toString().padStart(3, '0');
          switch (test.resultKind) {
            case 'succeed':
            case 'nonStrictSucceed':
            case 'noResult':
              this.passedTests += 1;
              test.resultStr = '通过';
              break;
            case 'failed':
            case 'exception':
            case 'codeDefect':
              test.resultStr = '未通过';
              break;
            default:
              this.unknownTests += 1;
              test.resultStr = '';
          }
          const newInputDetails = [];
          const newOutputDetails = [];
          if (test.resultKind === 'codeDefect' || test.resultKind === 'exception') {
            this.errorTests.push({
              desc: testInfoMap[test.testId.toString()].log.location
                ? `错误位置：${testInfoMap[test.testId.toString()].log.location.file} ${testInfoMap[test.testId.toString()].log.location.line}行${testInfoMap[test.testId.toString()].log.location.column}列`
                : '',
              kind: (test.resultKind === 'codeDefect') ? 1 : 2,
              functionName: func.functionName,
              testIndex: test.index,
            });
          }
          if (utility.isArrayType(test.detail.input)) {
            test.detail.input.forEach((input) => {
              input.variable = input.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
              if (input.value !== '') {
                input.value = ` = ${input.value}`;
              }
              if (input.category && input.variable) {
                newInputDetails.push({
                  category: `${input.category}:`,
                  value: '',
                  variable: '',
                });
                newInputDetails.push({
                  category: '',
                  value: input.value,
                  variable: input.variable,
                });
              } else {
                newInputDetails.push(input);
              }
            });
          }
          const failedDesc = [];
          if (utility.isArrayType(test.detail.output)) {
            test.detail.output.forEach((output) => {
              output.variable = output.variable.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g, '');
              if (output.value !== '' && output.value !== output.result) {
                failedDesc.push(`${output.variable}, 实际值=${output.result}, 期望值=${output.value}`);
              }
              // eslint-disable-next-line no-nested-ternary
              output.value = output.value !== ''
                ? ` = ${output.value}`
                : (output.result !== '' ? ` = ${output.result}` : '');
              // 处理变量值显示格式
              if (output.result !== '') {
                output.result = ` = ${output.result}`;
              }
              if (output.category && output.variable) {
                newOutputDetails.push({
                  category: `${output.category}:`,
                  value: '',
                  result: '',
                  variable: '',
                });
                newOutputDetails.push({
                  category: '',
                  value: output.value,
                  result: output.result,
                  variable: output.variable,
                });
              } else {
                newOutputDetails.push(output);
              }
            });
          }
          if (test.resultKind === 'failed' && failedDesc.length > 0) {
            this.errorTests.push({
              functionName: func.functionName,
              desc: failedDesc,
              kind: 0,
              testIndex: test.index,
            });
          }
          test.detail.input = newInputDetails;
          test.detail.output = newOutputDetails;
        });
        return result;
      }),
    };
    return result;
  }

  generateFileReport(data, output, template) {
    data.reportFooterText = this.reportFooterText;
    return (async () => {
      output = `${output}.docx`;
      const tpl = new Docx(path.join(this.baseDir, 'template', 'docx', `${template}.docx`));
      await tpl.generate(
        data,
        output,
        true
      );
      return output;
    })();
  }
}

module.exports = Reporter;
